#include "shader.h"

#include <glad/glad.h>
#include <glm/glm.hpp>

#include <string>
#include <iostream>

#include <utils.h>

#define SHADER_ERRLOG(x) std::cerr << "[SHADER] " << x << std::endl
#define SHADER_ERRLOG_EX(x) std::cerr << "[SHADER] " << x 
#define SHADER_ERRLOG_EX_BEG std::cerr
#define SHADER_ERRLOG_EX_END std::endl
#define SHADER_CHECK_STRING(x) if (!x || !*x) { SHADER_ERRLOG("error param " #x "."); return false; }
#define SHADER_STRING_LENGTH(x) (x ? strlen(x) : -1)

Shader::Shader() : id(0) {}
Shader::~Shader() { free(); }
bool Shader::loadPath(const GLchar *vertextPath, const GLchar *fragmentPath, const GLchar *geometryPath) {
  std::string vertex, fragment, geometry;
  if (!utils::loadFile(vertextPath, &vertex))
    return false;
  if (!utils::loadFile(fragmentPath, &fragment))
    return false;
  if (geometryPath) {
    if (!utils::loadFile(geometryPath, &geometry))
      return false;
  }
  return loadString(vertex.c_str(), fragment.c_str(), geometryPath ? geometry.c_str() : nullptr);
}
inline bool Shader::loadString(const GLchar *vertextString, const GLchar *fragmentString, const GLchar *geometryString) {
  return loadBuffer(vertextString, SHADER_STRING_LENGTH(vertextString)
    , fragmentString, SHADER_STRING_LENGTH(fragmentString)
    , geometryString, SHADER_STRING_LENGTH(geometryString));
}
bool Shader::loadBuffer(const GLchar *vertextString, const GLint vertextLength
      , const GLchar *fragmentString, const GLint fragmentLength
      , const GLchar *geometryString, const GLint geometryLength) {
  // check param
  SHADER_CHECK_STRING(vertextString);
  SHADER_CHECK_STRING(fragmentString);

  if (isOK()) {
    SHADER_ERRLOG("error program had created.");
    return false;
  }

  GLuint vertex = 0, fragment = 0, geometry = 0, program = 0;

  vertex = createShader(GL_VERTEX_SHADER, vertextString, vertextLength);
  if (vertex == 0) goto lb_err;

  fragment = createShader(GL_FRAGMENT_SHADER, fragmentString, fragmentLength);
  if (fragment == 0) goto lb_err;

  if (geometryString) {
    geometry = createShader(GL_GEOMETRY_SHADER, geometryString, geometryLength);
    if (geometry == 0) goto lb_err;
  }

  program = createProgram(vertex, fragment, geometry);

  lb_err:
  if (vertex) glDeleteShader(vertex);
  if (fragment) glDeleteShader(fragment);
  if (geometry) glDeleteShader(geometry);
  if (program) {
    id = program;
    return true;
  }
  return false;
}

GLuint Shader::createShader(GLenum type, const GLchar *code, const GLint length) {
  auto shader = glCreateShader(type);
  if (!shader) {
    SHADER_ERRLOG_EX("error glCreateShader ") << type << SHADER_ERRLOG_EX_END;
    return shader;
  }
  glShaderSource(shader, 1, &code, length <= 0 ? nullptr : &length);
  glCompileShader(shader);

  if (shaderHadCompileErrors(shader, type)) {
    glDeleteShader(shader);
    return 0;
  }
  return shader;
}
GLuint Shader::createProgram(GLuint vertex, GLuint fragment, GLuint geometry) {
  auto program = glCreateProgram();
  if (!program) {
    SHADER_ERRLOG("error glCreateProgram");
    return program;
  }
  if (vertex) glAttachShader(program, vertex);
  if (fragment) glAttachShader(program, fragment);
  if (geometry) glAttachShader(program, geometry);
  glLinkProgram(program);

  if (programHadLinkErrors(program)) {
    glDeleteProgram(program);
    return 0;
  }
  return program;
}
bool Shader::shaderHadCompileErrors(GLuint shader, GLenum type) {
  GLint success;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
  if (success) return false;

  const char *strType = "";
  switch (type) {
    #define __def(x) case x: strType = #x; break;
    __def(GL_VERTEX_SHADER)
    __def(GL_FRAGMENT_SHADER)
    __def(GL_GEOMETRY_SHADER)
    #undef __def
  }

  GLchar infoLog[1024];
  glGetShaderInfoLog(shader, 1024, nullptr, infoLog);
  SHADER_ERRLOG_EX("error glCompileShader ") << strType;
  if (!strType || !*strType) SHADER_ERRLOG_EX_BEG << type;
  SHADER_ERRLOG_EX_BEG << SHADER_ERRLOG_EX_END
      << infoLog << SHADER_ERRLOG_EX_END;
  return true;
}
bool Shader::programHadLinkErrors(GLuint program) {
  GLint success;
  glGetProgramiv(program, GL_LINK_STATUS, &success);
  if (success) return false;

  GLchar infoLog[1024];
  glGetProgramInfoLog(program, 1024, nullptr, infoLog);
  SHADER_ERRLOG_EX("error glLinkProgram ") << infoLog
      << SHADER_ERRLOG_EX_END;
  return true;
}

#undef SHADER_ERRLOG
#undef SHADER_ERRLOG_EX
#undef SHADER_ERRLOG_EX_END
#undef SHADER_CHECK_STRING
#undef SHADER_STRING_LENGTH